
#include <sstream>

#include "binindex.hpp"


namespace	apcab	{

#define	DEFAULT_BTREE_MINKEY	80	//// for PAGE-SIZE : 4096
//#define	DEFAULT_BTREE_MINKEY	160	//// for PAGE-SIZE : 8192
//#define	DEFAULT_BTREE_MINKEY	320	//// for PAGE-SIZE : 16384

BinIndex::BinIndex( const std::string db_file, DbEnv* dbenv_p, u_int32_t n_tables ) 
		:	_db_file( db_file ),
			_dbenv_p( dbenv_p ),
			_btree_minkey( DEFAULT_BTREE_MINKEY ),
			_n_tables( n_tables ),
			_dbp_cache( n_tables )
{
}

BinIndex::~BinIndex()
{
	closeAllTables();
}

///////////////////////////////////////////////////////////////////////
//

int 
BinIndex::put( const std::string& kpin,
		 const std::string& skey,
		 const RecordLocation&  loc )
{
	// STEP. get table name by kpin
	std::string	table_name = getTableName( kpin );

	// STEP. open table
	dbp_type dbp = openTable( table_name );

	// STEP. put record into table
	Dbt key_dbt, data_dbt;
	key_dbt.set_data( const_cast< char * >(skey.c_str()) );
	key_dbt.set_size( skey.size() );
	data_dbt.set_data( const_cast< RecordLocation * >(&loc) );
	data_dbt.set_size( sizeof(RecordLocation) );

	DbTxn *txn = NULL;
	_dbenv_p->txn_begin(NULL, &txn, 0);
	try {
		dbp->put(txn, &key_dbt, &data_dbt, 0);
		txn->commit(0);
	} catch (DbException &e) {
		txn->abort();

		std::ostringstream	msg;
		msg << "fail to insert data into table '"
		    << table_name
			<<"' -- " 
			<< e.what();
		throw std::runtime_error( msg.str() );
	}

	return	0;
}

int 
BinIndex::get( const std::string& kpin,
		 const std::string& skey,
			RecordLocation& loc )
{
	// STEP. get table name by kpin
	std::string	table_name = getTableName( kpin );

	// STEP. open table
	dbp_type dbp = openTable( table_name );

	// STEP. get record from table
	Dbt key_dbt, data_dbt;
	key_dbt.set_data( const_cast< char * >(skey.c_str()) );
	key_dbt.set_size( skey.size() );

	data_dbt.set_data( &loc );
	data_dbt.set_ulen( sizeof(RecordLocation) );
	data_dbt.set_flags(DB_DBT_USERMEM);

	try {
		dbp->get(NULL, &key_dbt, &data_dbt, 0);
	} catch (DbException &e) {
		if ( DB_NOTFOUND == e.get_errno() ) {
			return 0;	// NOT FOUND
		}

		std::ostringstream	msg;
		msg << "fail to get data from table '"
		    << table_name
			<<"' -- " 
			<< e.what();
		throw std::runtime_error( msg.str() );
	}

	return	0;
}

int 
BinIndex::erase( const std::string& kpin,
		   const std::string& skey )
{
	// STEP. get table name by kpin
	std::string	table_name = getTableName( kpin );

	// STEP. open table
	dbp_type dbp = openTable( table_name );

	// STEP. get record from table
	Dbt key_dbt, data_dbt;
	key_dbt.set_data( const_cast< char * >(skey.c_str()) );
	key_dbt.set_size( skey.size() );

	DbTxn *txn = NULL;
	_dbenv_p->txn_begin(NULL, &txn, 0);
	try {
		dbp->del(txn, &key_dbt, 0);
		txn->commit(0);
	} catch (DbException &e) {
		txn->abort();

		std::ostringstream	msg;
		msg << "fail to erase data from table '"
		    << table_name
			<<"' -- " 
			<< e.what();
		throw std::runtime_error( msg.str() );
	}

	return	0;
}



///////////////////////////////////////////////////////////////////////
//


const std::string   
BinIndex::getTableName( const std::string& kpin )
{
	u_int32_t	pin = getPinId( kpin );
	return 	getTableName( pin );
}


u_int32_t       
BinIndex::getPinId( const std::string& kpin )
{
	u_int32_t	pin = 0;

	if (kpin.size() > (sizeof(u_int32_t) + 2)) {
		memcpy( &pin, kpin.c_str() + 2, sizeof(u_int32_t) );
	}
	else if (kpin.size() >= sizeof(u_int32_t)) {
		memcpy( &pin, kpin.c_str() + (kpin.size() - sizeof(u_int32_t)), sizeof(u_int32_t) );
	}
	else if (kpin.size() > 0) {
		char*	p = (char *)&pin;
		const char*	q = kpin.c_str();
		int	i = 0, k = 0;
		while( true ) {
			*(p + i) = *(q + k);
			++i;
			if (i >= sizeof(u_int32_t)) break;
			++k;
			if (k >= kpin.size()) k = 0;
		}
	}
	else {
		pin = 0;
	}

	return pin;
}

std::string     
BinIndex::getTableName( u_int32_t pin )
{
	char	buf[512];

	u_int32_t	table_id = getTableId( pin );
	snprintf( buf, sizeof(buf) - 1, "X%02u", table_id );

	return std::string( buf );
}


u_int32_t       
BinIndex::getTableId( u_int32_t pin )
{
	return pin % _n_tables;
}


///////////////////////////////////////////////////////////////////////
//


BinIndex::dbp_type
BinIndex::openTable( const std::string& table_name )
{
	boost::mutex::scoped_lock	_scoped_lock( this->_dbp_cache_mutex );

	dbp_type	dbp;

	if ( _dbp_cache.exists( table_name ) ) {
		dbp = _dbp_cache.get( table_name );
		return dbp;
	}

	dbp = new Db( _dbenv_p, 0 );
	try {
		u_int32_t db_flags = DB_CREATE | DB_AUTO_COMMIT;

		dbp->set_bt_minkey( _btree_minkey );

		//dbp->set_pagesize( 16384 );

		dbp->open(NULL,       // Txn pointer
                  _db_file.c_str(), // File name
                  table_name.c_str(),       // Logical db name
                  DB_BTREE, // Database type (using btree)
                  db_flags, // Open flags
                  0);         // File mode. Using defaults


	}
	catch(DbException& e) {
		delete dbp;

		char	msg[ 1024 ];
		snprintf(msg, sizeof(msg) - 1, "fail to open table '%s' (DBFILE:%s cache-size:%lu) -- %s", table_name.c_str(), _db_file.c_str(), _dbp_cache.size(), e.what() );
		throw std::runtime_error( msg );
	}

	// put dbp into cache
	dbp_type	dbp_removed = _dbp_cache.put( table_name, dbp );
	if (dbp_removed) {
		dbp_removed->close( 0 );
		
		delete dbp_removed;
	}

	return dbp;
}


void
BinIndex::closeAllTables()
{
	boost::mutex::scoped_lock	_scoped_lock( this->_dbp_cache_mutex );

	dbp_cache_type::Key_List	_list( _dbp_cache.get_all_keys() );
	for( dbp_cache_type::Key_List_Iter liter = _list.begin(); liter != _list.end(); liter++ ) {
		dbp_type	dbp = _dbp_cache.get( *liter, false );

		dbp->close( 0 );
		delete dbp;
	}

	_dbp_cache.clear();
}



}	//// namespace apcab

